﻿// === Copyright 2008-2009 Jamie Macia et al. ===
// Module:      AttackRoll
// Created:     11/18/2008
// Author:      Jamie Macia
// Description: An extension of the DiceRoll specific to attacks and full attacks
// Revisions:
//      *jmacia 11/18/08 Issue 1: Created
//      *jmacia 12/30/08 Issue 2: Added pre- and post-attack events

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace JMacia.Gaming.d20.Core.Dice
{
    /// <summary>
    /// An extension of the DiceRoll class to represent attack rolls
    /// </summary>
    public class AttackRoll : DiceRoll
    {
        #region Protected Members
        protected int _iterativeAttacks = 0;
        protected int _attacksAtHighest = 1;
        protected int _criticalThreshold = 20;
        protected List<TimedModifier> _tempMods = new List<TimedModifier>();
        #endregion

        #region Events
        /// <summary>
        /// Allows modification of an attack roll just before the attack is rolled
        /// </summary>
        public event EventHandler<AttackEventArgs> BeforeAttackRolled;
        /// <summary>
        /// Allows modification of future attack rolls after an attack is rolled
        /// </summary>
        public event EventHandler<PostAttackEventArgs> AfterAttackRolled;
        #endregion

        #region Constructors

        /// <summary>
        /// Empty constructor
        /// </summary>
        public AttackRoll() : base() { }

        /// <summary>
        /// Constructs an attack roll from an attack bonus
        /// </summary>
        /// <param name="attackBonus">The highest attack bonus</param>
        public AttackRoll(int attackBonus) : base(attackBonus, new DiceSet(1, 20)) { }

        /// <summary>
        /// Constructs a full attack roll
        /// </summary>
        /// <param name="attackBonus">The highest attack bonus in the attack sequence</param>
        /// <param name="attacksAtHighest">The number of attacks at the highest bonus</param>
        /// <param name="iterativeAttacks">The number of iterative attacks at a cumulative -5 penalty per attack</param>
        public AttackRoll(int attackBonus, int attacksAtHighest, int iterativeAttacks)
            : base(attackBonus, new DiceSet(1, 20))
        {
            _attacksAtHighest = attacksAtHighest;
            _iterativeAttacks = iterativeAttacks;
        }

        /// <summary>
        /// Constructs a full attack roll including critical hit threshold
        /// </summary>
        /// <param name="attackBonus">The highest attack bonus in the attack sequence</param>
        /// <param name="attacksAtHighest">The number of attacks at the highest bonus</param>
        /// <param name="iterativeAttacks">The number of iterative attacks at a cumulative -5 penalty per attack</param>
        /// <param name="criticalThreshold">The lowest die roll that threatens a critical hit</param>
        public AttackRoll(int attackBonus, int attacksAtHighest, int iterativeAttacks, int criticalThreshold)
            : base(attackBonus, new DiceSet(1, 20))
        {
            _attacksAtHighest = attacksAtHighest;
            _iterativeAttacks = iterativeAttacks;
            _criticalThreshold = criticalThreshold;
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="copy">The AttackRoll of which to make a copy</param>
        public AttackRoll(AttackRoll copy)
            : base(copy)
        {
            this._iterativeAttacks = copy._iterativeAttacks;
            this._attacksAtHighest = copy._attacksAtHighest;
            this._criticalThreshold = copy._criticalThreshold;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// The highest attack bonus in the full attack sequence
        /// </summary>
        public int HighestBonus
        {
            get
            {
                return _modifier;
            }
            set
            {
                _modifier = value;
            }
        }

        /// <summary>
        /// The number of attacks in a full attack sequence at the 
        /// highest attack bonus
        /// </summary>
        /// <seealso cref="HighestBonus"/>
        public int AttacksAtHighest
        {
            get
            {
                return _attacksAtHighest;
            }
            set
            {
                _attacksAtHighest = value;
            }
        }

        /// <summary>
        /// The number of iterative attacks in the full attack sequence, 
        /// each at a cumulative -5 penalty from the highest bonus
        /// </summary>
        public int IterativeAttacks
        {
            get
            {
                return _iterativeAttacks;
            }
            set
            {
                _iterativeAttacks = value;
            }
        }

        /// <summary>
        /// The lowest die roll necessary to threaten a critical hit
        /// </summary>
        public int CriticalHitThreshold
        {
            get
            {
                return _criticalThreshold;
            }
            set
            {
                _criticalThreshold = value;
            }
        }

        #endregion

        #region Public Methods and Support Functions

        /// <summary>
        /// Parses a string into a full attack sequence
        /// </summary>
        /// <param name="rollDefinition">
        /// A string of the form X[/X][/...][/X-5][/X-10][...] where X is an integer.
        /// (Brackets denote an optional piece, not a literal bracket)</param>
        /// <exception cref="ArgumentException">Thrown if rollDefinition is not formatted as above</exception>
        public override void Parse(string rollDefinition)
        {
            rollDefinition.Replace(" ", string.Empty);
            string[] attacks = rollDefinition.Split('/');

            if (int.TryParse(attacks[0], out _modifier))
            {
                _iterativeAttacks = 0;
                _attacksAtHighest = 0;
                int currentMod = _modifier;
                for (int i = 1; i < attacks.Length; i++)
                {
                    int now;
                    if (!int.TryParse(attacks[i], out now))
                    {
                        StringBuilder message = new StringBuilder("Parse of attack roll modifier ");
                        message.Append(i + 1);
                        message.Append(" in the attack routine \"");
                        message.Append(rollDefinition);
                        message.Append("\" failed.  Attack modifiers must be valid integers delimited by forward slashes (/)");

                        throw new ArgumentException(message.ToString());
                    }

                    if (now == _modifier)
                    {
                        _attacksAtHighest++;
                    }
                    else
                    {
                        currentMod -= 5;
                        if (now == currentMod)
                        {
                            _iterativeAttacks++;
                        }
                        else
                        {
                            StringBuilder message = new StringBuilder("Illegitimate attack routine: ");
                            message.Append(rollDefinition);
                            message.Append(". Attack routines must be of the form X[/X][...][/X-5][X-10][...]");

                            throw new ArgumentException(message.ToString());
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Represents the full attack pattern in a string
        /// </summary>
        /// <returns>Sequence of integer attack bonuses delimited by forward slashes (/)</returns>
        public override string ToString()
        {
            StringBuilder build = new StringBuilder();
            int mod = _modifier;
            for (int i = 0; i < _attacksAtHighest; i++)
            {
                build.Append(mod);
                if (i < _attacksAtHighest - 1)
                {
                    build.Append('/');
                }
            }
            for (int i = 0; i < _iterativeAttacks; i++)
            {
                build.Append('/');
                mod -= 5;
                build.Append(mod);
            }

            return build.ToString();
        }

        /// <summary>
        /// Rolls a single attack
        /// </summary>
        /// <param name="individualRolls">The dice that were rolled</param>
        /// <returns>The total attack roll</returns>
        public override int Roll(out List<int> individualRolls)
        {
            int ret = base.Roll(out individualRolls);
            ret += RollTempMods(ref individualRolls);
            return ret;
        }

        /// <summary>
        /// Rolls the temporary modifiers and adds them to the attack roll in progress
        /// </summary>
        /// <param name="individualRolls">The dice that were rolled</param>
        /// <returns>The total temporary modifier roll</returns>
        protected virtual int RollTempMods(ref List<int> individualRolls)
        {
            List<int> temp;
            int val = 0;
            foreach (TimedModifier mod in _tempMods)
            {
                if (mod.Tick())
                {
                    val += mod.Modifier.Roll(out temp);
                    individualRolls.AddRange(temp);
                }
            }
            return val;
        }

        /// <summary>
        /// Remove the temporary modifiers that have expired
        /// </summary>
        protected virtual void RemoveExpiredMods()
        {
            foreach (TimedModifier mod in _tempMods)
            {
                if (mod.Persistent)
                {
                    if (mod.AttacksRemaining != null)
                    {
                        _tempMods.Remove(mod);
                    }
                }
                else
                {
                    _tempMods.Remove(mod);
                }
            }
        }

        /// <summary>
        /// Inner method to resolve attack rolls, including critical hit confirmation
        /// </summary>
        /// <param name="armorClass">
        /// The armor class of the attack's target</param>
        /// <param name="confirmCrit">
        /// True if this call is to confirm a critical hit,
        /// False otherwise</param>
        /// <returns>An AttackResult containing the result of the attack</returns>
        protected virtual AttackResult Roll(int armorClass, bool confirmCrit)
        {
            AttackEventArgs before = new AttackEventArgs();
            OnBeforeAttack(before);

            AttackResult result = new AttackResult();
            List<int> dummy;
            result.ModifiedRoll = Roll(out dummy);
            result.UnmodifiedRoll = dummy[0];

            if (result.UnmodifiedRoll == 20 || (result.ModifiedRoll >= armorClass && result.UnmodifiedRoll != 1))
            {
                result.Result = AttackResultEnum.Hit;
                if (!confirmCrit && result.UnmodifiedRoll >= _criticalThreshold)
                {
                    if (AfterAttackRolled != null)
                    {
                        PostAttackEventArgs args = new PostAttackEventArgs(AttackResultEnum.Hit, true);
                        AfterAttackRolled(this, args);
                        _tempMods.AddRange(args.Modifiers);
                    }
                    result.ParseConfirmation(Roll(armorClass, true));  //roll to confirm
                }// if we're not confirming a crit, and this attack threatens a crit
            }// if we roll a natural 20, or we hit the AC outright without rolling a 1
            else
            {
                result.Result = AttackResultEnum.Miss;
            }// else, we missed

            PostAttackEventArgs after = new PostAttackEventArgs(result);

            OnAfterAttack(after);
            result.Result = after.Result;
            result.ThreatensCritical = after.CritThreat;

            return result;
        }

        /// <summary>
        /// Rolls a single attack at the highest attack bonus
        /// </summary>
        /// <param name="againstAC">The armor class of the attack's target</param>
        /// <returns>An AttackResult denoting the specifics of the attack roll</returns>
        public AttackResult Roll(int againstAC)
        {
            return Roll(againstAC, false);
        }

        /// <summary>
        /// Rolls the entire full attack sequence
        /// </summary>
        /// <param name="armorClass">The armor class of the attacks' target</param>
        /// <returns>A System.List of AttackResults 
        /// containing the specifics of all the attacks</returns>
        public List<AttackResult> RollFullAttack(int armorClass)
        {
            List<AttackResult> vals = new List<AttackResult>();
            int originalModifier = _modifier;
            for (int i = 0; i < _attacksAtHighest; i++)
            {
                vals.Add(Roll(armorClass));
            }
            for (int i = 0; i < _iterativeAttacks; i++)
            {
                _modifier -= 5;
                vals.Add(Roll(armorClass));
            }
            _modifier = originalModifier;

            RemoveExpiredMods();

            return vals;
        }

        #endregion

        #region OnEvents Methods

        /// <summary>
        /// Calls the BeforeAttackRolled event
        /// </summary>
        /// <param name="e">the EventArgs variable</param>
        protected virtual void OnBeforeAttack(AttackEventArgs e)
        {
            if (BeforeAttackRolled != null)
            {
                BeforeAttackRolled(this, e);
                _tempMods.AddRange(e.Modifiers);
            }
        }

        /// <summary>
        /// Calls the AfterAttackRolled event
        /// </summary>
        /// <param name="e">the EventArgs variable</param>
        protected virtual void OnAfterAttack(PostAttackEventArgs e)
        {
            if (AfterAttackRolled != null)
            {
                AfterAttackRolled(this, e);
                _tempMods.AddRange(e.Modifiers);
            }
        }

        #endregion
    }

    /// <summary>
    /// A structure to contain all the specific of an attack roll against an AC
    /// </summary>
    public struct AttackResult
    {
        #region Private Members

        private AttackResultEnum _result;
        private int _modified;
        private int _unmodified;
        private int _confirmModified;
        private int _confirmUnmodified;
        private bool _critThreat;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor for an attack that did not threaten a critical hit
        /// </summary>
        /// <param name="result">Result of the attack (miss, hit, crit)</param>
        /// <param name="modified">The attack roll with attack bonus</param>
        /// <param name="unmodified">The unmodified die roll</param>
        public AttackResult(AttackResultEnum result, int modified, int unmodified)
        {
            _result = result;
            _modified = modified;
            _unmodified = unmodified;
            _confirmModified = 0;
            _confirmUnmodified = 0;
            _critThreat = false;
        }

        /// <summary>
        /// Constructor for an attack that may have threatened a critical hit
        /// </summary>
        /// <param name="result">Result of the attack (miss, hit, crit)</param>
        /// <param name="modified">The attack roll with attack bonus</param>
        /// <param name="unmodified">The unmodified die roll</param>
        /// <param name="confirmModified">The critical hit confirmation roll with attack bonus</param>
        /// <param name="confirmUnmodified">The unmodified die roll for the confirmation</param>
        public AttackResult(AttackResultEnum result, int modified, int unmodified, int confirmModified, int confirmUnmodified)
        {
            _result = result;
            _modified = modified;
            _unmodified = unmodified;
            _confirmModified = confirmModified;
            _confirmUnmodified = confirmUnmodified;
            _critThreat = (confirmUnmodified > 0);
        }

        /// <summary>
        /// Constructor for an attack that definitely threatened a critical hit
        /// </summary>
        /// <param name="modified">The attack roll with attack bonus</param>
        /// <param name="unmodified">The unmodified die roll</param>
        /// <param name="critResult">Result of the confirmation roll</param>
        public AttackResult(int modified, int unmodified, AttackResult critResult)
        {
            _modified = modified;
            _unmodified = unmodified;
            _confirmModified = critResult._modified;
            _confirmUnmodified = critResult._unmodified;
            _critThreat = true;
            if (critResult._result == AttackResultEnum.Hit)
            {
                _result = AttackResultEnum.CriticalHit;
            }
            else
            {
                _result = AttackResultEnum.Hit;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Changes the attack result given the result of the critical confirmation roll
        /// </summary>
        /// <param name="critResult">Result of the confirmation roll</param>
        public void ParseConfirmation(AttackResult critResult)
        {
            _confirmModified = critResult._modified;
            _confirmUnmodified = critResult._unmodified;
            _critThreat = true;
            if (critResult._result == AttackResultEnum.Hit)
            {
                _result = AttackResultEnum.CriticalHit;
            }
            else
            {
                _result = AttackResultEnum.Hit;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// The tri-state attack result (miss/hit/crit)
        /// </summary>
        public AttackResultEnum Result
        {
            get
            {
                return _result;
            }
            set
            {
                _result = value;
            }
        }

        /// <summary>
        /// The attack roll modified by the attack bonus
        /// </summary>
        public int ModifiedRoll
        {
            get
            {
                return _modified;
            }
            set
            {
                _modified = value;
            }
        }

        /// <summary>
        /// The attack's die roll without any modifier
        /// </summary>
        public int UnmodifiedRoll
        {
            get
            {
                return _unmodified;
            }
            set
            {
                _unmodified = value;
            }
        }

        /// <summary>
        /// The critical hit confirmation roll, modified by the attack bonus
        /// </summary>
        public int CritConfirmModifiedRoll
        {
            get
            {
                return _confirmModified;
            }
            set
            {
                _confirmModified = value;
            }
        }

        /// <summary>
        /// The critical hit confirmation die roll without any modifiers
        /// </summary>
        public int CritConfirmUnmodifiedRoll
        {
            get
            {
                return _confirmUnmodified;
            }
            set
            {
                _confirmUnmodified = value;
            }
        }

        /// <summary>
        /// True if this attack threatened a critical hit, false otherwise
        /// </summary>
        public bool ThreatensCritical
        {
            get
            {
                return _critThreat;
            }
            set
            {
                _critThreat = value;
            }
        }

        #endregion
    }

    /// <summary>
    /// Tri-state enumeration indicating the result of an attack
    /// </summary>
    public enum AttackResultEnum
    {
        /// <summary>
        /// The attack missed
        /// </summary>
        Miss,
        /// <summary>
        /// The attack scored a hit, but not a critical hit
        /// </summary>
        Hit,
        /// <summary>
        /// The attack scored a critical hit
        /// </summary>
        CriticalHit
    }

    public struct TimedModifier
    {
        private int? _remaining;
        private DiceRoll _modifier;
        private bool _persistent;

        public TimedModifier(DiceRoll modifier)
        {
            _modifier = modifier;
            _remaining = null;
            _persistent = false;
        }

        public TimedModifier(DiceRoll modifier, int duration)
        {
            _modifier = modifier;
            _remaining = duration;
            _persistent = false;
        }

        public TimedModifier(DiceRoll modifier, int duration, bool persistent)
        {
            _modifier = modifier;
            _remaining = duration;
            _persistent = persistent;
        }

        public int? AttacksRemaining
        {
            get
            {
                return _remaining;
            }
            set
            {
                _remaining = value;
            }
        }

        public DiceRoll Modifier
        {
            get
            {
                return _modifier;
            }
            set
            {
                _modifier = value;
            }
        }

        public bool Persistent
        {
            get
            {
                return _persistent;
            }
            set
            {
                _persistent = value;
            }
        }

        public bool Tick()
        {
            if (_remaining != null)
            {
                return (_remaining-- > 0);
            }
            return true;
        }
    }

    public class AttackEventArgs : EventArgs
    {
        public AttackEventArgs() : base() { }

        public AttackEventArgs(params TimedModifier[] modifiers)
            : base()
        {
            this.Modifiers.AddRange(modifiers);
        }

        public List<TimedModifier> Modifiers { get; set; }
    }

    public class PostAttackEventArgs : AttackEventArgs
    {
        public AttackResultEnum Result { get; set; }

        public bool CritThreat { get; set; }

        public PostAttackEventArgs() : base() { }

        public PostAttackEventArgs(params TimedModifier[] modifiers) : base(modifiers) { }

        public PostAttackEventArgs(AttackResultEnum result)
        {
            Result = result;
        }

        public PostAttackEventArgs(AttackResultEnum result, params TimedModifier[] modifiers)
            : base(modifiers)
        {
            Result = result;
        }

        public PostAttackEventArgs(AttackResultEnum result, bool threatens)
            : base()
        {
            Result = result;
            CritThreat = threatens;
        }

        public PostAttackEventArgs(AttackResultEnum result, bool threatens, params TimedModifier[] modifiers)
            : base(modifiers)
        {
            Result = result;
            CritThreat = threatens;
        }

        public PostAttackEventArgs(AttackResult result) : this(result.Result, result.ThreatensCritical) { }
    }
}